package com.trsvax.gwthello.services.dispatcher;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.tapestry5.internal.services.AssetResourceLocator;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.services.ComponentSource;
import org.apache.tapestry5.services.Dispatcher;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.RequestGlobals;
import org.apache.tapestry5.services.Response;
import org.slf4j.Logger;

import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.RPCRequest;
import com.google.gwt.user.server.rpc.RPCServletUtils;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import com.google.gwt.user.server.rpc.SerializationPolicyLoader;
import com.google.gwt.user.server.rpc.SerializationPolicyProvider;

public class GWTRPCDispatcher implements Dispatcher, SerializationPolicyProvider {
	private final Logger logger;
	private final RequestGlobals requestGlobals;
	private final ComponentSource componentSource;
	private final AssetResourceLocator assetResourceLocator;

	// private final ComponentResources componentResources;

	public GWTRPCDispatcher(Logger logger, RequestGlobals requestGlobals, ComponentSource componentSource,
			AssetResourceLocator assetResourceLocator) {
		this.logger = logger;
		this.requestGlobals = requestGlobals;
		this.componentSource = componentSource;
		this.assetResourceLocator = assetResourceLocator;
	}

	public boolean dispatch(Request request, Response response) throws IOException {
		String contentType = request.getHeader("Content-Type");
		if (contentType == null || contentType.indexOf("text/x-gwt-rpc") < 0) {
			return false;
		}

		try {
			String requestPayload = readContent(requestGlobals.getHTTPServletRequest());
			String responsePayload = processCall(requestPayload);
			PrintWriter pw = response.getPrintWriter("application/json; charset=utf-8");
			pw.print(responsePayload);
			pw.flush();
		} catch (ServletException e) {
			logger.error("servlet problem {}", e);
			throw new IOException("Servlet Problem " + e.getMessage());
		} catch (SerializationException e) {
			logger.error("serialization problem {}", e);
			throw new IOException("Serialization Problem " + e.getMessage());
		}
		return true;
	}

	protected String readContent(HttpServletRequest request) throws ServletException, IOException {
		return RPCServletUtils.readContentAsUtf8(request, true);
	}

	public String processCall(String payload) throws SerializationException {
		String context = requestGlobals.getRequest().getContextPath() + "/";

		String completeID = requestGlobals.getRequest().getPath().replaceFirst(context, "") + ":"
				+ requestGlobals.getHTTPServletRequest().getQueryString();
		Object component = componentSource.getComponent(completeID);
		if (component == null) {
			throw new SerializationException("no matching component for " + completeID);
		}
		try {
			RPCRequest rpcRequest = RPC.decodeRequest(payload, null, this);
			return RPC.invokeAndEncodeResponse(component, rpcRequest.getMethod(), rpcRequest.getParameters(),
					rpcRequest.getSerializationPolicy(), rpcRequest.getFlags());
		} catch (IncompatibleRemoteServiceException ex) {
			logger.info("An IncompatibleRemoteServiceException was thrown while processing this call. {}", ex);
			return RPC.encodeResponseForFailure(null, ex);
		}
	}

	protected boolean shouldCompressResponse(HttpServletRequest request, HttpServletResponse response,
			String responsePayload) {
		return RPCServletUtils.exceedsUncompressedContentLengthLimit(responsePayload);
	}

	public final SerializationPolicy getSerializationPolicy(String moduleBaseURL, String strongName) {
		SerializationPolicy serializationPolicy = null;

		/*
		 * SerializationPolicy serializationPolicy =
		 * getCachedSerializationPolicy( moduleBaseURL, strongName); if
		 * (serializationPolicy != null) { return serializationPolicy; }
		 */

		serializationPolicy = loadSerializationPolicy(requestGlobals.getHTTPServletRequest(), moduleBaseURL, strongName);

		if (serializationPolicy == null) {
			// Failed to get the requested serialization policy; use the default
			logger
					.info("WARNING: Failed to get the SerializationPolicy '"
							+ strongName
							+ "' for module '"
							+ moduleBaseURL
							+ "'; a legacy, 1.3.3 compatible, serialization policy will be used.  You may experience SerializationExceptions as a result.");
			serializationPolicy = RPC.getDefaultSerializationPolicy();
		}
		return serializationPolicy;
	}

	SerializationPolicy loadSerializationPolicy(HttpServletRequest request, String moduleBaseURL, String strongName) {
		// The request can tell you the path of the web app relative to the
		// container root.
		String contextPath = request.getContextPath();
		String path = requestGlobals.getRequest().getContextPath();

		String modulePath = null;
		if (moduleBaseURL != null) {
			try {
				modulePath = new URL(moduleBaseURL).getPath();
			} catch (MalformedURLException ex) {
				// log the information, we will default
				logger.info("Malformed moduleBaseURL: {} {}", moduleBaseURL, ex);
			}
		}

		SerializationPolicy serializationPolicy = null;

		/*
		 * Check that the module path must be in the same web app as the servlet
		 * itself. If you need to implement a scheme different than this,
		 * override this method.
		 */
		if (modulePath == null || !modulePath.startsWith(contextPath)) {
			String message = "ERROR: The module path requested, " + modulePath
					+ ", is not in the same web application as this servlet, " + contextPath
					+ ".  Your module may not be properly configured or your client and server code maybe out of date.";
			logger.info(message);
		} else {
			// Strip off the context path from the module base URL. It should be
			// a
			// strict prefix.
			String contextRelativePath = modulePath.substring(contextPath.length());

			String serializationPolicyFilePath = SerializationPolicyLoader
					.getSerializationPolicyFileName(contextRelativePath + strongName);

			Resource resource = null;
			InputStream is = null;
			try {
				resource = assetResourceLocator.findResourceForPath(serializationPolicyFilePath);
				is = resource.openStream();
			} catch (IOException e1) {
				logger.error("Resource lookup failed {} {}", serializationPolicyFilePath, e1);
			}

			try {
				if (is != null) {
					try {
						serializationPolicy = SerializationPolicyLoader.loadFromStream(is, null);
					} catch (ParseException e) {
						logger.info("ERROR: Failed to parse the policy file '{}' {}", serializationPolicyFilePath, e);
					} catch (IOException e) {
						logger.info("ERROR: Could not read the policy file '{}' {}", serializationPolicyFilePath, e);
					}
				} else {
					logger
							.info(
									"ERROR: The serialization policy file '{}' was not found; did you forget to include it in this deployment?",
									serializationPolicyFilePath);
				}
			} finally {
				if (is != null) {
					try {
						is.close();
					} catch (IOException e) {
						// Ignore this error
					}
				}
			}
		}

		return serializationPolicy;
	}
}
